/**
 * 基于FTFP协议，从服务器下载指定的文件
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>
#include <string.h>


#define DIR_PATH "files"
#define SERVER_IP "10.35.161.14"
#define SERVER_PORT 69

// ./down <文件名>
int main(int argc, char const *argv[])
{
    if(argc < 2) return -1;
    
    // 1. 定义下载的报文信息
    char buf[128] = "";
    sprintf(buf, "%c%c%s%c%s%c", 0, 1, argv[1], 0, "octet", 0);

    // 2. 定义TFTP服务器的地址
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(SERVER_PORT),
        .sin_addr.s_addr = inet_addr(SERVER_IP)
    };

    // 3. 发送下载请求报文
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    sendto(sockfd,  buf,  9+strlen(argv[1]), 0,
            (struct sockaddr *)&server, sizeof(server));

    // 4. 开始接收数据
    char databuf[516] = "";

    // 打开本地文件（新文件或旧文件则覆盖）
    // argv[1] 文件名
    // 存储位置在  DIR_PATH
    char filepath[128] = "";
    sprintf(filepath, "%s/%s", DIR_PATH, argv[1]);

    int fd = open(filepath, O_CREAT|O_WRONLY|O_TRUNC, 0644);
    
    // 定义已写入文件的块号
    uint16_t writed_block = 0;
    while(1){
        struct sockaddr_in data_addr;
        socklen_t data_addr_len = sizeof(data_addr);

        ssize_t datalen = recvfrom(sockfd, databuf, 516, 0,
                                   (struct sockaddr *)&data_addr, &data_addr_len);
        // 验证操作码是否为3
        if(databuf[1] == 3){
            uint16_t block_num = ntohs(  * ((uint16_t *)(databuf+2)) ); // 读取块号
            // 回 ACK
            databuf[1] = 4;
            sendto(sockfd, databuf, 4, 0, (struct sockaddr *)&data_addr, sizeof(data_addr));
            
            // 将数据写入到本地文件中
            if(block_num > writed_block){  // 避免重复写入(UDP本身不靠实，另外，在Linux虚拟机，可能发送2次请求)
                write(fd, databuf+4, datalen-4);
                writed_block = block_num;
                printf("正在接收并写入： 第 %d 块数据(%ld)\n", block_num,datalen-4);
            }
           
            if(datalen < 516){
                break;
            }
        }else if(databuf[1] == 5){ 
            printf("error: %s\n", databuf+4);
            remove(filepath); // 请求失败时删除文件
            break;
        }
    }

    printf("--over--\n");
    close(sockfd);
    close(fd);

    return 0;
}
